﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using STSdb.Data;

//STS db Multi-Threading Layer
namespace STSdb.MTL
{
    /// <summary>
    /// STSdb Multi-Threading Layer
    /// </summary>
    public class STSdbMTLengine:IDisposable
    {
        
        StorageEngine _engine = null;

        /// <summary>
        /// This one is hidden from end-user, so it should never become public
        /// </summary>
        TransactionsCoordinator _transactionsCoordinator = null;
        STSdb.MTL.Scheme _scheme = null;

        /// <summary>
        /// IN DEV
        /// Instantiate it, if you want to have Log of all Db operations 
        /// in separate log files
        /// </summary>
        public EngineTransactionsLog TransactionLog
        {
            set { this._transactionsCoordinator.TransactionsLog = value; }
        }

        /// <summary>
        /// Create engine from one file
        /// </summary>
        /// <param name="dbPath"></param>
        public STSdbMTLengine(string dbPath)
        {
            this._engine = StorageEngine.FromFile(dbPath);
            this.Init();
        }

        /// <summary>
        /// Creates engine from volume chain. For simplified version look other overloads.
        /// </summary>
        /// <param name="volumeChain"></param>
        public STSdbMTLengine(STSdb.Volumes.VolumeChain volumeChain)
        {            
            this._engine = StorageEngine.FromChain(volumeChain);
            this.Init();
        }


        #region "Creating Engine from multiple files"

        STSdb.Volumes.VolumeChain volumeChain = null;
        string vc_dbPath = String.Empty;
        string vc_dbName = String.Empty;
        string vc_dbNameExtension = String.Empty;
        ulong vc_quota = 1 * 1024 * 1024;

        /// <summary>
        /// <para>STSdb.MTL.STSdbMTLengine e1 = new STSdb.MTL.STSdbMTLengine(@"D:\temp\STS\", "mydb", "db", 100 * 1024 * 1024);</para>
        /// Files will reside in D:\temp\STS\
        /// <para>D:\temp\STS\mydb.00001.db</para>
        /// <para>D:\temp\STS\mydb.00002.db</para>
        /// <para>D:\temp\STS\mydb.00003.db</para>
        /// <para>...</para>
        /// </summary>
        /// <param name="dbPath">D:\temp\STS</param>
        /// <param name="dbName">test</param>
        /// <param name="dbNameExtension">sts</param>
        /// <param name="maximumFileSize">32*1024*1024 = 32 MB</param>
        public STSdbMTLengine(string dbPath, string dbName, string dbNameExtension, ulong maximumFileSize)
        {

            this.vc_dbName = dbName;
            this.vc_dbNameExtension = dbNameExtension;
            this.vc_dbPath = dbPath;
            this.vc_quota = maximumFileSize;

            volumeChain = new STSdb.Volumes.VolumeChain();
            volumeChain.QuotaExceed += new STSdb.Volumes.VolumeChain.QuotaExceedDelegate(chain_QuotaExceed);

            //restoring db from chain files
            foreach (string fileName in System.IO.Directory.GetFiles(dbPath + System.IO.Path.DirectorySeparatorChar.ToString(), dbName + ".*." + dbNameExtension))
            {
                volumeChain.Add(new STSdb.Volumes.Volume(fileName));
            }

            this._engine = StorageEngine.FromChain(volumeChain);
            this.Init();
        }
               

        private void chain_QuotaExceed(STSdb.Volumes.VolumeChain sender, out string newVolumeFileName, out ulong volumeQuota)
        {
            //newVolumeFileName = String.Format(@"D:\temp\STS\test.{0:00000}.stsdb", sender.Volumes.Length);
            newVolumeFileName = String.Format(this.vc_dbPath + System.IO.Path.DirectorySeparatorChar.ToString() + this.vc_dbName + ".{0:00000}" + "." + this.vc_dbNameExtension, sender.Volumes.Length);
            //volumeQuota = 32 * 1024 * 1024; //max 32MB size of each new volume
            volumeQuota = this.vc_quota; //max 32MB size of each new volume
        }

        #endregion

        /// <summary>
        /// Creates in-memory Engine with predefined quota
        /// </summary>
        /// <param name="quota"></param>
        public STSdbMTLengine(ulong quota)
        {
            this._engine = StorageEngine.FromMemory(quota);
            this.Init();
        }

        private void Init()
        {
            //TransactionsCoordinator, who initiates SchemaCoordinator etc.
            this._transactionsCoordinator = new TransactionsCoordinator(this._engine);
            //initializing Schema and giving it transaction coordinator, via TC schema object can access Engine and Schema Coordinator.
            //User of STSdbMTLengine still has limited access to GetTransaction, Scheme and Dispose, and has no access to the deeper level - what is good.
            this._scheme = new Scheme(this._transactionsCoordinator);

            ////Setting up Transaction Log. By default it is null, if not instantiated by the user
            //this._transactionsCoordinator.TransactionsLog = this.TransactionLog;

            #region "Finishing tables exhales"

            this._transactionsCoordinator.SchemaCoordinator.StsTablesCompacter.RestoreCompactingState();

            #endregion


            /////////////// SNAPSHOTS CAN BE A PROBLEM IF WE COMPACT DATA (MOVING IT FROM ONE TBL TO ANOTHER) because they are stored in the same raw file. 
            //So, Distributed transactions must be revisited later again.

            #region "Restoring not finished transactions"

            //Checking not finished transactions
            var notFinishedTransactionsList = DistributedTransactionCoordinator.GetFailedTransactionsList();

            if (notFinishedTransactionsList != null && notFinishedTransactionsList.Count() > 0)
            {
                try
                {
                    //restong rollbacked transaction tables from their snapshots.
                    this._transactionsCoordinator.RestoreTablesFromSnapshots(notFinishedTransactionsList);
                }
                catch (Exception ex)
                {
                    //Rollbacked transaction snapshots can't be restored. It's a serious problem.   
                    //We have to stop engine and find out what is going on.
                    this._transactionsCoordinator.StopEngine();
                    throw new Exception("STSdb.MTL.Init: Can't restore not rollbacked transaction tables from the snapshot");
                }               
                
            }

            #endregion

           
        }

        /// <summary>
        /// Initialize Managed Transaction for every other operation with database
        /// </summary>
        /// <returns></returns>
        public Transaction GetTransaction()
        {
            //This is checked in this._transactionsCoordinator.GetTransaction();
            //if (!this._transactionsCoordinator.DbIsOperatable)
            //    return null;

            return this._transactionsCoordinator.GetTransaction();
        }

        /// <summary>
        /// Simple backup without support of XKeyMaps. first creates text file. then other engine instance can restore from
        /// this text file.
        /// </summary>
        /// <param name="backupFilePath"></param>
        /// <returns></returns>
        public STSdb.MTL.Backup.Backup Backup(string backupFilePath)
        {
            return new Backup.Backup(this._engine, backupFilePath);
        }

        /// <summary>
        /// Gives managed access to the DB scheme
        /// </summary>
        public STSdb.MTL.Scheme Scheme
        {
            get
            {
                return this._scheme;
            }
        }
        
        /// <summary>
        /// Must be called, while engine stops, all parallel pending threads will be finilized.
        /// </summary>
        public void Dispose()
        {
            DistributedTransactionCoordinator.DisposeDistributedTransactionDb();
            this._transactionsCoordinator.StopEngine();            
        }


        
    }
}
